package com.njbw.design_pattern.created.singleton;

import java.io.Serializable;

/**
 * 双重检查锁实现单例模式
 * Double-Checked Locking (DCL) 是一种用于延迟初始化单例对象的设计模式，旨在减少同步的开销，同时确保线程安全。
 * DCL 的核心思想是在实例化单例对象时，先检查实例是否已经存在，如果不存在，则进入同步块，在同步块内再次检查实例是否存在，
 * 如果仍然不存在，则创建实例。这样可以确保只有在第一次创建实例时才会进行同步，之后的访问则不需要同步，从而提高性能。
 *
 * @author huang hou chun
 * @since JDK 1.8 <br>
 */
public class DclSingleton implements Serializable {

    // 1.持有一个 JVM 全局唯一的实例,使用 volatile 关键字修饰，确保 INSTANCE 变量的可见性和禁止指令重排序优化
    // 创建对象不是一个原子操作，即使使用了双重检查锁，也可能在创建的过程中产生半初始化状态
    // volatile 关键字的作用有两个：1.保证变量的内存可见性 2.禁止指令重排序优化，保证有序性
    // 事实上，DCL 在 Java 1.5 之前是有问题的，因为当时的 Java 内存模型（JMM）允许指令重排序优化，
    // 这可能导致一个线程看到一个未完全初始化的实例，从而引发问题。
    // volatile 如果不加可能会出现半初始化的对象
    // 现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题, 为了兼容性我们加上
    // 解决的方法很简单，只要把对象 new 操作和初始化操作设计为原子操作，就自然能禁止重排序
    private static volatile DclSingleton INSTANCE;

    // 2.为了避免别人随意的创建，我们需要私有化构造器
    private DclSingleton() {
        // 防止通过反射来破坏单例
        // 升级的版本 --> 不要让人使用反射创建
        if (null != INSTANCE) {
            throw new RuntimeException("该对象是单例的，无法创建多个！！！");
        }
    }

    // 3.暴露一个方法，用来获取实例
    // 第一次创建需要双锁，一旦创建好了，就不再需要上锁
    // 事实上，获取单例并没有线程安全的问题（读没有，写才有）
    public static DclSingleton getInstance() {
        // 第一次检查
        if (null == INSTANCE) {
            synchronized (DclSingleton.class) {
                // 第二次检查
                if (null == INSTANCE) {
                    INSTANCE = new DclSingleton();
                }
            }
        }
        return INSTANCE;
    }

    // 需要添加这么一个方法，防止序列化和反序列化破坏单例
    private Object readResolve() {
        return INSTANCE;
    }
}
